package base;

import java.awt.EventQueue;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.NoSuchElementException;
import java.util.Properties;

import loadbalancer.LoadBalancer1;
import loadbalancer.LoadBalancer2;
import loadbalancer.LoadBalancer3;
import loadbalancer.LoadBalancer4;
import loadbalancer.LoadBalancer5;
import loadbalancer.LoadBalancer6;
import loadbalancer.LoadBalancer7;
import loadbalancer.LoadBalancer8;

/**
 * Simulate the behavior of different LoadBalancer
 * 
 * @author rweiller
 * 
 */
public class Simulator
{

	// Config file
	public static final String	CONFIG_FILE		= "trunk/src/config";
	public static Properties	prop;

	// Simulator needs
	public Gauss				gauss;
	public Exponentiel			exp;
	public Source				src;
	public LoadBalancer			lb;
	public Servers				servers;

	//Results file
	public static final String	RESULTS_FILE	= "trunk/results/results.csv";
	public static final String	RESULTS_FILE_COMPLEMENTS	= "trunk/results/results_complements.csv";

	/**
	 * Retrieve property in the configuration file
	 * 
	 * @param name a property name
	 * @return the correspondent value
	 */
	public static int getProperty(String name)
	{
		return Integer.parseInt(prop.getProperty(name));
	}

	/**
	 * Launch the Simulator.
	 */
	public static void main(String[] args)
	{
		EventQueue.invokeLater(new Runnable()
		{
			@Override
			public void run()
			{
				try
				{
					Simulator simulator = new Simulator();

					// Scanner
					System.out.println("-- Start --\n");
					//Scanner sc = new Scanner(System.in);

					// Init
					String s = "";
					int delai_gauss = simulator.gauss.nextInt();
					int delai_exp = simulator.exp.nextInt();
					//int time = 0;

					// Simulator run
					System.out.println("Running...");
					while (true)
					{
						// Wait the user to go on
						//s = sc.nextLine();
						//System.out.println("." + time++ + ".");

						// Check exit
						if (s.compareTo("quit") == 0)
							break;

						// Verification
						if (delai_gauss < 0)
						{
							delai_gauss = simulator.gauss.nextInt();
						}
						if (delai_exp < 0)
						{
							delai_exp = simulator.exp.nextInt();
						}

						//System.out.println("g = " + delai_gauss + "\t e = " + delai_exp);

						// Run
						if (!simulator.src.isEmpty())
						{
							delai_gauss = simulator.runPush(delai_gauss);
						}
						if (!simulator.lb.isEmpty())
						{
							delai_exp = simulator.runPull(delai_exp);
						}
						if (!simulator.servers.isEmpty())
						{
							simulator.servers.proceed();
						}

						// Check end of the application
						if (simulator.src.isEmpty() && simulator.lb.isEmpty() && simulator.servers.isEmpty())
						{
							simulator.servers.writeToFile(RESULTS_FILE);
							simulator.servers.writeToFileComplements(RESULTS_FILE_COMPLEMENTS);
							System.out.println("Program end.");
							System.exit(0);
							break;
						}
					}
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the Simulator.
	 * 
	 * @throws IOException
	 */
	public Simulator() throws IOException
	{
		initialize();
	}

	/**
	 * Generate a LoadBalancer, depending on the config file
	 * 
	 * @param choice correspond to sim.loadbalancer
	 * @param nb_server correspond to sim.servers
	 * @return loadbalancer used by simulator
	 */
	public LoadBalancer generateLoadBalancer(int choice, int nb_server)
	{
		LoadBalancer lb = null;
		switch (choice)
		{
			case 1:
				lb = new LoadBalancer1();
				break;
			case 2:
				lb = new LoadBalancer2();
				break;
			case 3:
				lb = new LoadBalancer3();
				break;
			case 4:
				lb = new LoadBalancer4();
				break;
			case 5:
				lb = new LoadBalancer5();
				break;
			case 6:
				lb = new LoadBalancer6();
				break;
			case 7:
				lb = new LoadBalancer7();
				break;
			case 8:
				lb = new LoadBalancer8();
				break;
			default:
				System.err.println("Config file : sim.loadbalancer have to be between 1 - 8 inclusive");
				break;
		}
		return lb;
	}

	/**
	 * Generate and return the source, depending on the source choice
	 * 
	 * @param source a choice between 1 and 5 inclusive
	 * @return source used by simulator
	 * @throws IOException
	 */
	private Source generateSource(int source) throws IOException
	{
		Source src = null;

		if (source > 5 || source < 1)
		{
			throw new IOException("Config file : sim.source have to be between 1 and 5 inclusive");
		}

		src = new Source(	getProperty("sim.source." + source + ".seed"),
							getProperty("sim.source.range"),
							getProperty("sim.source.sample"));

		return src;
	}

	/**
	 * Initialize Simulator.
	 * 
	 * @throws IOException
	 */
	private void initialize() throws IOException
	{

		loadConfig();

		gauss = new Gauss(	getProperty("sim.gauss.seed.x"),
							getProperty("sim.gauss.seed.y"),
							getProperty("sim.gauss.m"),
							getProperty("sim.gauss.e"));

		exp = new Exponentiel(	getProperty("sim.exp.seed"),
								getProperty("sim.exp.m"));

		src = generateSource(getProperty("sim.source"));

		lb = generateLoadBalancer(	getProperty("sim.loadbalancer"),
									getProperty("sim.server"));

		servers = new Servers(	getProperty("sim.server"),
								getProperty("sim.server.processpower"));

	}

	/**
	 * Load the configuration file
	 */
	public void loadConfig()
	{
		try
		{
			Simulator.prop = new Properties();
			InputStream is = new FileInputStream(CONFIG_FILE);
			Simulator.prop.load(is);
		}
		catch (FileNotFoundException e)
		{
			System.out.println("Configuration file not found.");
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * If delai == 0, pull an element from the loadbalancer in the servers
	 * 
	 * @param delai a time to wait before pulling
	 * @see LoadBalancer
	 * @return delai - 1
	 */
	public int runPull(int delai)
	{
		if (delai == 0)
		{
			//System.out.println("-- PULL --");
			try
			{
				lb.send(servers);
			}
			catch (NoSuchElementException e)
			{
				System.err.println(e.getMessage());
			}

		}
		lb.incElementTime();
		return delai - 1;
	}

	/**
	 * If delai == 0, push an element from the source in the loadbalancer
	 * 
	 * @param delai a time to wait before pushing
	 * @see Source
	 * @return delai - 1
	 */
	public int runPush(int delai)
	{
		if (delai == 0)
		{
			//System.out.println("-- PUSH --");
			src.push(lb);
		}
		return delai - 1;
	}
}
